﻿/*
 * 名称：数据库操作类
 * 简介：主要用于数据的访问操作，封闭了一些常用的操作，可以实现对数据库底层的封装。
 * 作者：郝伟
 * 邮箱：hwaust@126.com
 *   QQ：117511560
 * 
 * 2022/03/02 添加了GetStringSEX函数用于对Object的支持。
 * 2021/10/10 添加了GetCount函数，并为GetObject添加了可选参数。
 * 
 * 
 * V2.0 
 * 2018-12-23 重构
 * 基本思想：功能单一化，只对SQL处理，清除无用的功能。
 * 
 * v1.0
 * 2013-2-19 Wind
 * - 添加了GetColumnNames函数。
 * 2011-11-22	Wind -做了大量的修改，主要集中在返回数据为不null及2种数据库的整合上。
 * 2011-7-22	Wind	- 追加GetAllDataBase方法，获取服务器中所有数据库表。
 * 2008-10-27	Wind	- 初始版本，支持ACCESS和SQLServer数据库
 *
 * 
数据库中的所有数据的删除操作。
--再关闭所有外键约束  
exec sp_msforeachtable "alter table ? nocheck constraint all"  
--然后删除数据  
exec sp_msforEachTable "TRUNCATE TABLE?"  
--再启用所有外键约束  
exec sp_msforeachtable "alter table ? check constraint all"
 * 
*/


using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Data.Common;
using System.Windows.Forms;

namespace WindGoes6.Database
{
    /// <summary>
    /// 封装了数据常用的方法，大大减化了数据库的操作。
    /// </summary>
    public class SQLManager
    {
        #region 字段
        string commandText = "";
        DbConnection cn;
        DbCommand cmd;
        DbDataReader reader;
        DbDataAdapter adapter;

        #endregion

        #region 属性

        /// <summary>
        /// 当前对象的连接字符串。
        /// </summary>
        public string CurrentConnectionString { get; set; }
        /// <summary>
        /// 在不指定连接字符串时，所有实例共用的静态连接字符串。
        /// </summary>
        public static string CommonConnectionString { get; set; }

        /// <summary>
        /// 是否显示出错对话框。
        /// </summary>
        public bool ShowErrorDialog { get; set; }

        /// <summary>
        /// 需要执行的SQL语句。
        /// </summary>
        public string CommandText
        {
            set { commandText = value; }
            get { return commandText; }
        }



        #endregion

        #region 构造函数

        void InitDataBase(string con, string command)
        {
            CurrentConnectionString = con;
            cn = new SqlConnection(con);
            adapter = new SqlDataAdapter();
            cmd = cn.CreateCommand();
            cmd.CommandText = command;
        }

        /// <summary>
        /// 用于处理数据库连接的
        /// </summary>
        public SQLManager()
        {
            CurrentConnectionString = CommonConnectionString;
            InitDataBase(CurrentConnectionString, "");
        }

        /// <summary>
        /// 用于处理数据库连接的
        /// </summary>
        /// <param name="db">数据库类型。</param>
        /// <param name="con">连接字符串。</param>
        public SQLManager(string con)
        {
            InitDataBase(con, "");
        }


        /// <summary>
        /// 用于处理数据库连接的类
        /// </summary>
        /// <param name="con">连接字符串。</param>
        /// <param name="sql">需要执行的SQL字符串。</param> 
        public SQLManager(string con, string sql)
        {
            InitDataBase(con, sql);
        }

        #endregion


        #region 数据库的打开的关闭操作
        /// <summary>
        /// 打开数据库连接.
        /// </summary>
        /// <returns>打开成功返回true,否则返回false</returns>
        public bool Open()
        {
            if (cn == null)
            {
                InitDataBase(CurrentConnectionString, commandText);
            }
            if (cn.State == ConnectionState.Open)
                return true;

            try
            {
                cn.Open();
            }
            catch (Exception e1)
            {
                if (ShowErrorDialog)
                    MessageBox.Show("连接数据库失败，请检查连接后重新连接！错误信息：\n" + e1.Message, "连接失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            return true;
        }

        /// <summary>
        /// 关闭数据库连接.
        /// </summary>
        /// <returns>关闭成功返回true,否则返回false</returns>
        public bool Close()
        {
            try
            {
                cn.Close();
            }
            catch
            {
                return false;
            }
            return true;
        }

        #endregion

        #region 对数据库的操作

        /// <summary>
        /// 一般Update和Insert可以用这个方法来实现.
        /// </summary>
        /// <returns>用bool型表示执行结果, true表示成功,否则失败</returns>
        public bool NonQuery()
        {
            if (!Open())
                return false;
            try
            {
                cmd = cn.CreateCommand();
                cmd.CommandText = commandText;
                cmd.ExecuteNonQuery();
            }
            catch (Exception exp)
            {
                if (ShowErrorDialog)
                    MessageBox.Show("操作失败，原因：" + exp.Message);
                Close();
                return false;
            }

            return Close();
        }

        /// <summary>
        /// 与NonQuery的区别在于，这个函数返回的是字符串，用于表示的操作的结果。
        /// </summary>
        /// <returns>用bool型表示执行结果, true表示成功,否则失败</returns>
        public string NonQueryString()
        {
            string str = "S";
            if (!Open())
                return "Open Database Failed";
            try
            {
                cmd.CommandText = commandText;
                cmd.ExecuteNonQuery();
                str = "Successful";
            }
            catch (Exception exp)
            {
                str = exp.Message;
            }
            if (!Close())
                return "Close Database Failed";
            return str;
        }


        /// <summary>
        /// 快速执行SQL语句，去除了打开和关闭数据库的操作，同时去除了所有验证，从而大大提高了数据操作速度。
        /// 但是执行时务必自行保证验证各细节正确，否则会造成程序出错。
        /// </summary>
        public void FastExecuteNonQuery()
        {
            cmd.CommandText = commandText;
            cmd.ExecuteNonQuery();
        }

        /// <summary>
        /// 一般Select语句用这个方法来实现, 返回结果为字符串二维数组.
        /// </summary>
        /// <returns>若读取失败，则返回空数据。</returns>
        public string[][] GetStrings()
        {
            List<string[]> data = new List<string[]>();
            if (!Open())
                return data.ToArray();
            try
            {
                cmd.Connection = cn;
                cmd.CommandText = commandText;
                reader = cmd.ExecuteReader();
                while (reader.Read())
                {
                    string[] strs = new string[reader.FieldCount];
                    for (int i = 0; i < strs.Length; i++)
                        strs[i] = reader[i].ToString();
                    data.Add(strs);
                }
                reader.Close();
            }
            catch (Exception ex)
            {
                if (ShowErrorDialog)
                    MessageBox.Show("读取失败，错误原因：\n" + ex.Message);
                return data.ToArray();
            }
            if (!Close())
                return data.ToArray();
            return data.ToArray();
        }

        /// <summary>
        /// 用于存储非字符串型数据的 key-value 结构。
        /// </summary>
        public Dictionary<string, object> NonStringData = new Dictionary<string, object>();

        /// <summary>
        /// 一般Select语句用这个方法来实现, 返回结果为字符串二维数组。
        /// 对于非字符串的内容，数据会显示格式为 "System.Object:32"，其中"System.Object:"为固定内容，
        /// "32"表示索引，可以使用 NonStringData[32] 访问得到对应的对象。
        /// </summary>
        /// <returns>若读取失败，则返回空数据。</returns>
        public string[][] GetStringsEX()
        {
            List<string[]> list = new List<string[]>();

            // 打开失败返回长度为空的字符串数组
            if (!Open())
                return list.ToArray();

            try
            {
                NonStringData.Clear();
                cmd.Connection = cn;
                cmd.CommandText = commandText;
                reader = cmd.ExecuteReader();
                while (reader.Read())
                {
                    string[] array = new string[reader.FieldCount];
                    for (int i = 0; i < array.Length; i++)
                    {
                        object obj = reader[i];
                        if (obj is byte[])
                        {
                            array[i] = $"System.Object:{NonStringData.Keys.Count}";
                            NonStringData.Add(NonStringData.Count.ToString(), obj);
                        }
                        else
                        {
                            array[i] = reader[i].ToString();
                        }
                    }

                    list.Add(array);
                }

                reader.Close();
            }
            catch (Exception ex)
            {
                if (ShowErrorDialog)
                {
                    MessageBox.Show("读取失败，错误原因：\n" + ex.Message);
                }

                return list.ToArray();
            }

            if (!Close())
            {
                return list.ToArray();
            }

            return list.ToArray();
        }

        /// <summary>
        /// 一般Select语句用这个方法来实现, 返回结果为object的二维数组.
        /// </summary>
        /// <returns>如果返回为null表示读取失败</returns>
        public object[][] GetObjects()
        {
            List<object[]> data = new List<object[]>();
            if (!Open())
                return data.ToArray();
            try
            {
                cmd.Connection = cn;
                cmd.CommandText = commandText;
                reader = cmd.ExecuteReader();
                while (reader.Read())
                {
                    object[] objs = new object[reader.FieldCount];
                    for (int i = 0; i < objs.Length; i++)
                        objs[i] = reader[i];
                    data.Add(objs);
                }
                reader.Close();
            }
            catch
            {
                return data.ToArray();
            }
            if (!Close())
                return data.ToArray();
            return data.ToArray();
        }



        /// <summary>
        /// 只返回第一条记录的数据，如果没有数据，返回长度为0的数据（不为null)。
        /// </summary>
        /// <returns>string[]</returns>
        public string[] GetRow()
        {
            string[][] data = GetStrings();
            return data.Length > 0 ? data[0] : new string[0];
        }

        /// <summary>
        /// 返回第一列的数据，适合只有一列的操作。
        /// </summary>
        /// <returns></returns>
        public string[] GetColumn()
        {
            string[][] data = GetStrings();

            if (data.Length == 0)
                return new string[0];

            string[] str = new string[data.Length];
            for (int i = 0; i < data.Length; i++)
                str[i] = data[i][0];
            return str;
        }

        /// <summary>
        /// 对于只要查询一个数据的查询可以用这个方法。
        /// </summary>
        /// <param name="sql">需要执行的SQL，返回结果为唯一的int型，如果为null则使用CommondText的值。</param>
        /// <returns>使用try-catch处理了，如果没有读取成功，返回为null，否则返回对象。</returns>
        public object GetObject(string sql = null)
        {
            object obj;
            if (!Open())
                return null;
            try
            {
                cmd = cn.CreateCommand();
                cmd.CommandText = sql ?? commandText;
                obj = cmd.ExecuteScalar();
            }
            catch
            {
                return null;
            }
            if (!Close())
                return null;
            return obj;
        }


        /// <summary>
        /// 使用try-catch处理了，用于查询count，返回的值是int型，执行失败返回-1，否则返回自然数。
        /// <param name="sql">需要执行的SQL，返回结果为唯一的int型，如果为null则使用CommondText的值。</param>
        /// </summary>
        /// <returns>如果没有读取成功，返回为null，否则返回对象。</returns>
        public int GetCount(string sql = null)
        {
            int res = -1;
            if (!Open())
                return res;
            try
            {
                cmd = cn.CreateCommand();
                cmd.CommandText = sql ?? commandText;
                object obj = cmd.ExecuteScalar();
                res = int.Parse(obj.ToString());
            }
            catch { }
            Close();
            return res;
        }


        /// <summary>
        /// 获得表中所有列的信息
        /// </summary>
        /// <param name="tableName">要得到信息的表名</param>
        /// <returns></returns>
        public FieldInfo[] GetFieldInfo(string tableName)
        {
            cmd.CommandText = "select * from " + tableName;
            if (!Open())
            {
                return null;
            }
            reader = cmd.ExecuteReader();
            FieldInfo[] fis = new FieldInfo[reader.FieldCount];
            for (int i = 0; i < fis.Length; i++)
            {
                fis[i] = new FieldInfo(i, reader.GetName(i), reader.GetFieldType(i));
            }
            reader.Close();
            Close();
            return fis;
        }

        /// <summary>
        /// 返回数据集。
        /// </summary>
        /// <returns></returns>
        public DataSet GetDataSet()
        {
            DataSet ds = new DataSet();
            if (Open())
            {
                adapter = new SqlDataAdapter(commandText, CurrentConnectionString);
                adapter.Fill(ds);
            }
            return ds;
        }

        #endregion

        /// <summary>
        /// 返回服务器中所有数据库的名称，只支持Sql数据库。
        /// </summary>
        /// <returns></returns>
        public List<string> GetAllDataBaseNames()
        {
            List<string> list = new List<string>();
            CommandText = "select name from master..sysdatabases";
            string[] data = GetColumn();
            for (int i = 0; i < data.Length; i++)
            {
                string name = data[i].ToLower();
                if (name != "master" && name != "tempdb" && name != "model" && name != "msdb")
                    list.Add(data[i]);
            }
            return list;
        }

        /// <summary>
        /// 返回当前数据库中所有的表名。
        /// </summary>
        /// <returns></returns>
        public string[] GetAllTableNames()
        {
            if (!Open())
                return new string[] { };

            //commandText = "SELECT name FROM SYSOBJECTS WHERE XTYPE = 'U' " ;
            DataTable dt = cn.GetSchema("Tables");
            string[] tableNames = new string[dt.Rows.Count];
            for (int i = 0; i < dt.Rows.Count; i++)
                if (dt.Rows[i][3].ToString().ToLower() == "base table")
                    tableNames[i] = dt.Rows[i][2].ToString();

            Close();

            return tableNames;
        }

        /// <summary>
        /// 返回指定表的所有列的列名。
        /// </summary>
        /// <param name="tableName">指定表的表名。</param>
        /// <returns></returns>
        public string[] GetColumnNames(string tableName)
        {
            if (!Open())
                return new string[] { };

            CommandText = "select top 1 * from " + tableName;
            DataTable dt = GetDataSet().Tables[0];
            string[] names = new string[dt.Columns.Count];
            for (int i = 0; i < dt.Columns.Count; i++)
            {
                names[i] = dt.Columns[i].ColumnName;
            }

            Close();

            return names;
        }
    }
}
